/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package org.python.pydev.parser.fastparser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.python.pydev.core.Tuple3;
import org.python.pydev.core.docutils.ParsingUtils;
import org.python.pydev.core.docutils.PySelection;
import org.python.pydev.core.docutils.SyntaxErrorException;
import org.python.pydev.core.log.Log;
import com.aptana.shared_core.string.FastStringBuffer;
import com.aptana.shared_core.structure.Tuple;
/**
* This parser is a bit different from the others, as its output is not an AST, but a structure defining the scopes
* in a document (used for doing the scope selection action).
*
* @author fabioz
*/
public class ScopesParser {
public static class ScopeEntry {
public final int type;
public final boolean open;
public final int id;
public final int offset;
public ScopeEntry(int id, int type, boolean open, int offset) {
this.type = type;
this.open = open;
this.id = id;
this.offset = offset;
}
public void toString(FastStringBuffer temp) {
if (open) {
temp.append('[');
temp.append(id);
temp.append(' ');
} else {
temp.append(' ');
temp.append(id);
temp.append(']');
}
}
}
public static class Scopes {
public static int TYPE_COMMENT = 1;
public static final int TYPE_PEER = 2;
public static final int TYPE_STRING = 3;
public static final int TYPE_MODULE = 4;
public static final int TYPE_SUITE = 5;
/**
* Structure mapping the offset to the scope entries at that offset.
*
* At a given position, opening entries should appear before the position and closing entries after the position.
*/
private Map<Integer, List<ScopeEntry>> offsetToEntries = new HashMap<Integer, List<ScopeEntry>>();
private int scopeId = 0;
private Map<Integer, Tuple<ScopeEntry, ScopeEntry>> idToStartEnd = new HashMap<Integer, Tuple<ScopeEntry, ScopeEntry>>();
private List<ScopeEntry> getAtOffset(int offset) {
List<ScopeEntry> list = offsetToEntries.get(offset);
if (list == null) {
list = new ArrayList<ScopeEntry>();
offsetToEntries.put(offset, list);
}
return list;
}
public IRegion getScopeForSelection(final int offset, final int len) {
final int endOffset = offset + len - 1;
for (int i = offset; i >= 0; i--) {
//We have to get a scope that starts before the current offset and ends after offset+len
//If it's the same, we must expand to an outer scope!
List<ScopeEntry> list = offsetToEntries.get(i);
if (list != null) {
ListIterator<ScopeEntry> listIterator = list.listIterator(list.size());
while (listIterator.hasPrevious()) {
ScopeEntry scopeEntry = listIterator.previous();
if (scopeEntry.open) {
//Only interested in the opening ones at this point
Tuple<ScopeEntry, ScopeEntry> tup = idToStartEnd.get(scopeEntry.id);
if (i == offset && endOffset == tup.o2.offset) {
continue;
}
if (endOffset > tup.o2.offset) {
continue;
}
return new Region(tup.o1.offset, tup.o2.offset - tup.o1.offset + 1);
}
}
}
}
return null;
}
public int startScope(int offset, int type) {
scopeId++;
List<ScopeEntry> list = getAtOffset(offset);
ScopeEntry startEntry = new ScopeEntry(scopeId, type, true, offset);
list.add(startEntry);
idToStartEnd.put(scopeId, new Tuple(startEntry, null));
return scopeId;
}
public void endScope(int id, int offset, int type) {
offset--;
List<ScopeEntry> list = getAtOffset(offset);
ScopeEntry endEntry = new ScopeEntry(id, type, false, offset);
idToStartEnd.get(id).o2 = endEntry;
list.add(endEntry);
}
public FastStringBuffer debugString(Object doc) {
ParsingUtils utils = ParsingUtils.create(doc);
FastStringBuffer temp = new FastStringBuffer(utils.len() + (utils.len() / 10));
int len = utils.len();
for (int i = 0; i < len; i++) {
char c = utils.charAt(i);
printEntries(temp, i, true);
temp.append(c);
printEntries(temp, i, false);
}
return temp;
}
private void printEntries(FastStringBuffer temp, int i, boolean opening) {
List<ScopeEntry> list = offsetToEntries.get(i);
if (list != null) {
for (ScopeEntry e : list) {
if (e.open == opening) {
e.toString(temp);
}
}
}
}
}
public static Scopes createScopes(IDocument doc) {
ScopesParser scopesParser = new ScopesParser(doc);
try {
return scopesParser.createScopes();
} catch (SyntaxErrorException e) {
throw new RuntimeException(e);
}
}
private Scopes scopes;
private SortedMap<Integer, Integer> lineOffsetToIndent = new TreeMap<Integer, Integer>();
private IDocument doc;
private ScopesParser(IDocument doc) {
this.scopes = new Scopes();
this.doc = doc;
try {
TabNannyDocIterator nannyDocIterator = new TabNannyDocIterator(doc, true, false);
while (nannyDocIterator.hasNext()) {
Tuple3<String, Integer, Boolean> next = nannyDocIterator.next();
this.lineOffsetToIndent.put(next.o2, next.o1.length());
}
} catch (BadLocationException e1) {
throw new RuntimeException(e1);
}
}
private Scopes createScopes() throws SyntaxErrorException {
int globalScope = this.scopes.startScope(0, Scopes.TYPE_MODULE);
int offset = createInternalScopes(ParsingUtils.create(doc, true), 0);
this.scopes.endScope(globalScope, offset, Scopes.TYPE_MODULE);
return this.scopes;
}
private int createInternalScopes(ParsingUtils parsingUtils, int offsetDelta) {
int docLen = parsingUtils.len();
int offset = 0;
FastStringBuffer buf = new FastStringBuffer();
FastStringBuffer lineMemo = new FastStringBuffer();
int memoStart = 0;
int id;
for (; offset < docLen; offset++) {
char ch = parsingUtils.charAt(offset);
switch (ch) {
case '#':
id = this.scopes.startScope(offsetDelta + offset, Scopes.TYPE_COMMENT);
offset = parsingUtils.eatComments(buf.clear(), offset);
this.scopes.endScope(id, offsetDelta + offset, Scopes.TYPE_COMMENT);
break;
case '{':
case '[':
case '(':
int baseOffset = offset;
try {
offset = parsingUtils.eatPar(offset, buf.clear(), ch); //If a SyntaxError is raised here, we won't create a scope!
id = this.scopes.startScope(offsetDelta + baseOffset + 1, Scopes.TYPE_PEER);
try {
String cs = doc.get(offsetDelta + baseOffset + 1, offset - baseOffset - 1);
createInternalScopes(ParsingUtils.create(cs, true), offsetDelta + baseOffset + 1);
} catch (BadLocationException e1) {
Log.log(e1);
}
this.scopes.endScope(id, offsetDelta + offset, Scopes.TYPE_PEER);
} catch (SyntaxErrorException e2) {
}
break;
case '\'':
//Fallthrough
case '\"':
baseOffset = offset;
try {
offset = parsingUtils.eatLiterals(buf.clear(), offset); //If a SyntaxError is raised here, we won't create a scope!
id = this.scopes.startScope(offsetDelta + baseOffset, Scopes.TYPE_STRING);
this.scopes.endScope(id, offsetDelta + offset + 1, Scopes.TYPE_STRING);
} catch (SyntaxErrorException e1) {
}
break;
case ':':
if (PySelection.startsWithIndentToken(lineMemo.toString().trim())) {
SortedMap<Integer, Integer> subMap = lineOffsetToIndent.tailMap(offsetDelta + memoStart + 1);
Integer level = lineOffsetToIndent.get(offsetDelta + memoStart);
if (level == null) {
//It's a ':' inside a parens
continue;
}
Set<Entry<Integer, Integer>> entrySet = subMap.entrySet();
boolean found = false;
id = this.scopes.startScope(memoStart + level, Scopes.TYPE_SUITE);
int id2 = -1;
for (int j = offset + 1; j < docLen; j++) {
char c = parsingUtils.charAt(j);
if (Character.isWhitespace(c)) {
continue;
}
if (c == '#') {
j = parsingUtils.eatComments(null, j);
continue;
}
id2 = this.scopes.startScope(offsetDelta + j, Scopes.TYPE_SUITE);
break;
}
for (Entry<Integer, Integer> entry : entrySet) {
if (level >= entry.getValue()) {
found = true;
Integer scopeEndOffset = entry.getKey();
try {
int line = doc.getLineOfOffset(scopeEndOffset);
if (line > 0) {
//We want it to end at the end of the previous line (not at the start of the next scope)
IRegion lineInformation = doc.getLineInformation(line - 1);
scopeEndOffset = lineInformation.getOffset() + lineInformation.getLength();
}
} catch (BadLocationException e) {
Log.log(e);
}
this.scopes.endScope(id, scopeEndOffset, Scopes.TYPE_SUITE);
if (id2 > 0) {
this.scopes.endScope(id2, scopeEndOffset, Scopes.TYPE_SUITE);
}
break;
}
}
if (!found) {
//Ends at the end of the document!
this.scopes.endScope(id, offsetDelta + parsingUtils.len(), Scopes.TYPE_SUITE);
if (id2 > 0) {
this.scopes.endScope(id2, offsetDelta + parsingUtils.len(), Scopes.TYPE_SUITE);
}
}
}
break;
case '\r':
//Fallthrough
case '\n':
//Note that we don't add the \r nor \n to the memo (but we clear it if the line did not end with a \).
if (lineMemo.length() > 0 && lineMemo.lastChar() != '\\') {
lineMemo.clear();
}
break;
default:
if (lineMemo.length() == 0) {
memoStart = offset;
}
lineMemo.append(ch);
break;
}
}
return offset;
}
}